package com.xxl.job.admin.service.impl;

import java.text.MessageFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.kaver.job.core.biz.AdminBiz;
import com.kaver.job.core.biz.model.AddJobParam;
import com.kaver.job.core.biz.model.HandleCallbackParam;
import com.kaver.job.core.biz.model.JobInfo;
import com.kaver.job.core.biz.model.QueryJobParam;
import com.kaver.job.core.biz.model.RegistryParam;
import com.kaver.job.core.biz.model.ReturnT;
import com.kaver.job.core.biz.model.StopJobParam;
import com.kaver.job.core.biz.model.UpdateJobParam;
import com.kaver.job.core.handler.IJobHandler;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
import com.xxl.job.admin.service.XxlJobService;

/**
 * @author xuxueli 2017-07-27 21:54:20
 */
@Service
public class AdminBizImpl implements AdminBiz {
    private static Logger logger = LoggerFactory.getLogger(AdminBizImpl.class);

    @Resource
    public XxlJobLogDao xxlJobLogDao;
    @Resource
    private XxlJobInfoDao xxlJobInfoDao;
    @Resource
    private XxlJobRegistryDao xxlJobRegistryDao;
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Resource
    XxlJobService xxlJobService;
    @Autowired
    RedissonClient redissonClient;
    
    private static Executor executor = new ThreadPoolExecutor(20, 200, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    @Override
    public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
        for (HandleCallbackParam handleCallbackParam: callbackParamList) {
            ReturnT<String> callbackResult = callback(handleCallbackParam);
            logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
                    (callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
        }

        return ReturnT.SUCCESS;
    }

    private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
        // valid log item
        XxlJobLog log = xxlJobLogDao.load(handleCallbackParam.getLogId());
        if (log == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
        }
        if (log.getHandleCode() > 0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback.");     // avoid repeat callback, trigger child job etc
        }

        // trigger success, to trigger child job
        String callbackMsg = null;
        if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
            XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
            if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
                callbackMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";

                String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
                for (int i = 0; i < childJobIds.length; i++) {
                    int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
                    if (childJobId > 0) {

                        JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
                        ReturnT<String> triggerChildResult = ReturnT.SUCCESS;

                        // add msg
                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
                                (i+1),
                                childJobIds.length,
                                childJobIds[i],
                                (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
                                triggerChildResult.getMsg());
                    } else {
                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
                                (i+1),
                                childJobIds.length,
                                childJobIds[i]);
                    }
                }

            }
        }

        // handle msg
        StringBuffer handleMsg = new StringBuffer();
        if (log.getHandleMsg()!=null) {
            handleMsg.append(log.getHandleMsg()).append("<br>");
        }
        if (handleCallbackParam.getExecuteResult().getMsg() != null) {
            handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
        }
        if (callbackMsg != null) {
            handleMsg.append(callbackMsg);
        }

        if (handleMsg.length() > 15000) {
            handleMsg = new StringBuffer(handleMsg.substring(0, 15000));  // text最大64kb 避免长度过长
        }

        // success, save log
        log.setHandleTime(new Date());
        log.setHandleCode(handleCallbackParam.getExecuteResult().getCode());
        log.setHandleMsg(handleMsg.toString());
        xxlJobLogDao.updateHandleInfo(log);

        return ReturnT.SUCCESS;
    }

    private boolean isNumeric(String str){
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @Override
    public ReturnT<String> registry(RegistryParam registryParam) {

        // valid
        if (!StringUtils.hasText(registryParam.getRegistryGroup())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
        }

        int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
        if (ret < 1) {
            xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());

            // fresh
            freshGroupRegistryInfo(registryParam);
        }
        return ReturnT.SUCCESS;
    }

    @Override
    public ReturnT<String> registryRemove(RegistryParam registryParam) {

        // valid
        if (!StringUtils.hasText(registryParam.getRegistryGroup())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
        }

        int ret = xxlJobRegistryDao.registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
        if (ret > 0) {

            // fresh
            freshGroupRegistryInfo(registryParam);
        }
        return ReturnT.SUCCESS;
    }

    private void freshGroupRegistryInfo(RegistryParam registryParam){
        // Under consideration, prevent affecting core tables
    }

    @Override
    public ReturnT<String> addJob(AddJobParam addJobParam) {
        if (addJobParam == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "param Illegal.");
        }
        XxlJobGroup jobGroup = xxlJobGroupDao.loadByName(addJobParam.getAppname());
        if (jobGroup == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "appname Illegal.");
        }
        String key = "addJob_" + addJobParam.getAppname() + addJobParam.getExecutorHandler() + addJobParam.getJobDesc();
        RLock lock = redissonClient.getLock(key);
        if (lock.tryLock()) {
            try {
                XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadDynamicJob(jobGroup.getId(),addJobParam.getExecutorHandler(), addJobParam.getJobDesc());
                if (xxlJobInfo != null) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job already exist.");
                }
                XxlJobInfo jobInfo = new XxlJobInfo();
                jobInfo.setJobCron(addJobParam.getJobCron());
                jobInfo.setJobDesc(addJobParam.getJobDesc());
                jobInfo.setJobGroup(jobGroup.getId());
                jobInfo.setExecutorBlockStrategy(addJobParam.getExecutorBlockStrategy());
                jobInfo.setExecutorFailRetryCount(addJobParam.getExecutorFailRetryCount());
                jobInfo.setExecutorHandler(addJobParam.getExecutorHandler());
                jobInfo.setExecutorParam(addJobParam.getExecutorParam());
                jobInfo.setExecutorRouteStrategy(addJobParam.getExecutorRouteStrategy());
                jobInfo.setExecutorTimeout(addJobParam.getExecutorTimeout());
                jobInfo.setDynamicJob("1");
                jobInfo.setAuthor("admin");
                jobInfo.setGlueType("BEAN");
                ReturnT<String> returnT = xxlJobService.add(jobInfo);
                if (returnT.getCode() != ReturnT.SUCCESS_CODE) {
                    return returnT;
                }
                int jobId = jobInfo.getId();
                if (addJobParam.isOneCount()) {
                    if (addJobParam.getDelay() <= 0) {
                        executor.execute(()->{
                            String executorParam = addJobParam.getExecutorParam();
                            if(executorParam == null) {
                                executorParam = "";
                            }
                            JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.API, -1, null, addJobParam.getExecutorParam(), null);
                        });
                    } else {
                        return xxlJobService.start(Integer.valueOf(returnT.getContent()), addJobParam.isOneCount(), addJobParam.getDelay());
                    }
                } else {
                    return xxlJobService.start(Integer.valueOf(returnT.getContent()), addJobParam.isOneCount(), addJobParam.getDelay());
                }
                return ReturnT.SUCCESS;
            } finally {
                lock.unlock();
            }
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "lock fail.");
        }
    }

    @Override
    public ReturnT<String> stopJob(StopJobParam stopJobParam) {
        if (stopJobParam == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "param Illegal.");
        }
        XxlJobGroup jobGroup = xxlJobGroupDao.loadByName(stopJobParam.getAppname());
        if (jobGroup == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "appname Illegal.");
        }
        XxlJobInfo jobInfo =  xxlJobInfoDao.loadDynamicJob(jobGroup.getId(),stopJobParam.getExecutorHandler(), stopJobParam.getJobDesc());
        if (jobInfo == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "param Illegal jobInfo is null.");
        }
        return xxlJobService.stop(jobInfo.getId());
    }

    @Override
    public ReturnT<String> updateJob(UpdateJobParam updateJobParam) {
        if (updateJobParam == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "param Illegal.");
        }
        XxlJobGroup jobGroup = xxlJobGroupDao.loadByName(updateJobParam.getAppname());
        if (jobGroup == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "appname Illegal.");
        }
        XxlJobInfo jobInfo =  xxlJobInfoDao.loadDynamicJob(jobGroup.getId(),updateJobParam.getExecutorHandler(), updateJobParam.getJobDesc());
        if (jobInfo == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "param Illegal jobInfo is null.");
        }
        if (updateJobParam.getExecutorBlockStrategy() != null) {
            jobInfo.setExecutorBlockStrategy(updateJobParam.getExecutorBlockStrategy());
        }
        jobInfo.setExecutorFailRetryCount(updateJobParam.getExecutorFailRetryCount());
        if (updateJobParam.getExecutorParam() != null) {
            jobInfo.setExecutorParam(updateJobParam.getExecutorParam());
        }
        if (updateJobParam.getExecutorRouteStrategy() != null) {
            jobInfo.setExecutorRouteStrategy(updateJobParam.getExecutorRouteStrategy());
        }
        jobInfo.setExecutorTimeout(updateJobParam.getExecutorTimeout());
        if (updateJobParam.getJobCron() != null) {
            jobInfo.setJobCron(updateJobParam.getJobCron());
        }
        return xxlJobService.update(jobInfo);
    }

    @Override
    public ReturnT<JobInfo> queryJob(QueryJobParam queryJobParam) {
        if (queryJobParam == null) {
            return new ReturnT<JobInfo>(ReturnT.FAIL_CODE, "param Illegal.");
        }
        XxlJobGroup jobGroup = xxlJobGroupDao.loadByName(queryJobParam.getAppName());
        if (jobGroup == null) {
            return new ReturnT<JobInfo>(ReturnT.FAIL_CODE, "appname Illegal.");
        }
        XxlJobInfo jobInfo =  xxlJobInfoDao.loadDynamicJob(jobGroup.getId(),queryJobParam.getHandler(), queryJobParam.getSerialNo());
        if (jobInfo == null) {
            return new ReturnT<JobInfo>(ReturnT.FAIL_CODE, "param Illegal jobInfo is null.");
        }
        JobInfo info = new JobInfo();
        BeanUtils.copyProperties(jobInfo, info);
        return new ReturnT<JobInfo>(info);
    }

}
